#pragma once
#include "Log.hpp"
#include "RecordFile.hpp"

namespace zzz{
// Auto select
class IOObj
{
public:
  template<typename T>
  static void WriteFileB(FILE *fp, const T *src, zsize size) {
    IOObject<T>::WriteFileB(fp, src, size);
  }
  template<typename T>
  static void ReadFileB(FILE *fp, T *dst, zsize size) {
    IOObject<T>::ReadFileB(fp, dst, size);
  }
  template<typename T>
  static void WriteFileB(FILE *fp, const T &src) {
    IOObject<T>::WriteFileB(fp, src);
  }
  template<typename T>
  static void ReadFileB(FILE *fp, T &dst) {
    IOObject<T>::ReadFileB(fp, dst);
  }
  template<typename T>
  static void WriteFileR(RecordFile &fp, const zint32 label, const T &src) {
    IOObject<T>::WriteFileR(fp, label, src);
  }
  template<typename T>
  static void ReadFileR(RecordFile &fp, const zint32 label, T &dst) {
    IOObject<T>::ReadFileR(fp, label, dst);
  }
  template<typename T>
  static void WriteFileR1By1(RecordFile &fp, const zint32 label, const T &src) {
    IOObject<T>::WriteFileR1By1(fp, label, src);
  }
  template<typename T>
  static void ReadFileR1By1(RecordFile &fp, const zint32 label, T &dst) {
    IOObject<T>::ReadFileR1By1(fp, label, dst);
  }
  template<typename T>
  static void WriteFileR(RecordFile &fp, const T &src) {
    IOObject<T>::WriteFileR(fp, src);
  }
  template<typename T>
  static void ReadFileR(RecordFile &fp, T &dst) {
    IOObject<T>::ReadFileR(fp, dst);
  }
  template<typename T>
  static void WriteFileR(RecordFile &fp, const zint32 label, const T *src, zsize size) {
    IOObject<T>::WriteFileR(fp, label, src, size);
  }
  template<typename T>
  static void ReadFileR(RecordFile &fp, const zint32 label, T *dst, zsize size) {
    IOObject<T>::ReadFileR(fp, label, dst, size);
  }
  template<typename T>
  static void WriteFileR(RecordFile &fp, const T *src, zsize size) {
    IOObject<T>::WriteFileR(fp, src, size);
  }
  template<typename T>
  static void ReadFileR(RecordFile &fp, T *dst, zsize size) {
    IOObject<T>::ReadFileR(fp, dst, size);
  }
  template<typename T>
  static void CopyData(T* dst, const T* src, zsize size) {
    IOObject<T>::CopyData(dst, src, size);
  }
};

// Default IOObject, simple call function in T
// Should be specialized if needed.
template<typename T>
class IOObject
{
public:
  static void CopyData(T* dst, const T* src, zsize size) {
    for (zsize i=0; i<size; i++) dst[i]=src[i];
  }
  static void WriteFileB(FILE *fp, const T *src, zsize size) {
    T::WriteFileB(fp, src, size);
  }
  static void ReadFileB(FILE *fp, T *dst, zsize size) {
    T::ReadFileB(fp, dst, size);
  }
  static void WriteFileB(FILE *fp, const T &src) {
    T::WriteFileB(fp, src);
  }
  static void ReadFileB(FILE *fp, T &dst) {
    T::ReadFileB(fp, dst);
  }
  static void WriteFileR(RecordFile &fp, const zint32 label, const T &src) {
    T::WriteFileR(fp, label, src);
  }
  static void ReadFileR(RecordFile &fp, const zint32 label, T &dst) {
    T::ReadFileR(fp, label, dst);
  }
  static void WriteFileR(RecordFile &fp, const T &src) {
    T::WriteFileR(fp, src);
  }
  static void ReadFileR(RecordFile &fp, T &dst) {
    T::ReadFileR(fp, dst);
  }
  static void WriteFileR(RecordFile &fp, const zint32 label, const T *src, zsize size) {
    T::WriteFileR(fp, label, src, size);
  }
  static void ReadFileR(RecordFile &fp, const zint32 label, T *dst, zsize size) {
    T::ReadFileR(fp, label, dst, size);
  }
  static void WriteFileR(RecordFile &fp, const T *src, zsize size) {
    T::WriteFileR(fp, src, size);
  }
  static void ReadFileR(RecordFile &fp, T *dst, zsize size) {
    T::ReadFileR(fp, dst, size);
  }
};

#define SIMPLE_IOOBJECT(T) \
template<>\
class IOObject<T>\
{\
public:\
  static void WriteFileB(FILE *fp, const T *src, zsize size) {\
    fwrite(src, sizeof(T), size, fp); \
  }\
  static void ReadFileB(FILE *fp, T *dst, zsize size) {\
    ZCHECK_EQ(fread(dst, sizeof(T), size, fp), size); \
  }\
  static void WriteFileB(FILE *fp, const T &src) {\
    fwrite(&src, sizeof(T), 1, fp); \
  }\
  static void ReadFileB(FILE *fp, T &dst) {\
    ZCHECK_EQ(fread(&dst, sizeof(T), 1, fp), 1); \
  }\
  static void WriteFileR(RecordFile &fp, const zint32 label, const T &src) {\
    fp.Write(label, &src, sizeof(T), 1); \
  }\
  static void ReadFileR(RecordFile &fp, const zint32 label, T &dst) {\
    fp.Read(label, &dst, sizeof(T), 1); \
  }\
  static void WriteFileR(RecordFile &fp, const T &src) {\
    fp.Write(&src, sizeof(T), 1); \
  }\
  static void ReadFileR(RecordFile &fp, T &dst) {\
    fp.Read(&dst, sizeof(T), 1); \
  }\
  static void WriteFileR(RecordFile &fp, const zint32 label, const T *src, zsize size) {\
    fp.Write(label, src, sizeof(T), size); \
  }\
  static void ReadFileR(RecordFile &fp, const zint32 label, T *dst, zsize size) {\
    fp.Read(label, dst, sizeof(T), size); \
  }\
  static void WriteFileR(RecordFile &fp, const T *src, zsize size) {\
    fp.Write(src, sizeof(T), size); \
  }\
  static void ReadFileR(RecordFile &fp, T *dst, zsize size) {\
    fp.Read(dst, sizeof(T), size); \
  }\
  static void CopyData(T* dst, const T* src, zsize size) {\
    memcpy(dst,src,sizeof(T)*size); \
  }\
};

SIMPLE_IOOBJECT(char);
SIMPLE_IOOBJECT(long);

SIMPLE_IOOBJECT(zint8);
SIMPLE_IOOBJECT(zuint8);
SIMPLE_IOOBJECT(zint16);
SIMPLE_IOOBJECT(zuint16);
SIMPLE_IOOBJECT(zint32);
SIMPLE_IOOBJECT(zuint32);
SIMPLE_IOOBJECT(zint64);
SIMPLE_IOOBJECT(zuint64);
SIMPLE_IOOBJECT(float);
SIMPLE_IOOBJECT(double);
SIMPLE_IOOBJECT(long double);

template<typename T>
class IOObject<vector<T*> > {
public:
  /// When the object inside vector is not SIMPLE_IOOBJECT, it must access 1 by 1,
  /// instead of access as a raw array.
  static const int RF_SIZE = 1;
  static const int RF_DATA = 2;
  static void WriteFileR1By1(RecordFile &rf, const zint32 label, const vector<T*> &src) {
    zuint64 len = src.size();
    rf.WriteChildBegin(label);
      IOObj::WriteFileR(rf, RF_SIZE, len);
      rf.WriteRepeatBegin(RF_DATA);
      for (zuint64 i = 0; i < len; ++i) {
        rf.WriteRepeatChild();
        IOObj::WriteFileR(rf, *(src[i]));
      }
      rf.WriteRepeatEnd();
    rf.WriteChildEnd();
  }
  static void ReadFileR1By1(RecordFile &rf, const zint32 label, vector<T*> &dst) {
    for (zuint i = 0; i < dst.size(); ++i)
      delete dst[i];
    dst.clear();
    if (!rf.LabelExist(label)) {
      return;
    }
    rf.ReadChildBegin(label);
      zuint64 len;
      IOObj::ReadFileR(rf, RF_SIZE, len);
      if (len != 0) {
        dst.reserve(len);
      }
      rf.ReadRepeatBegin(RF_DATA);
      while(rf.ReadRepeatChild()) {
        T* v = new T;
        IOObj::ReadFileR(rf, *v);
        dst.push_back(v);
      }
      rf.ReadRepeatEnd();
      ZCHECK_EQ(dst.size(), len) << "The length recorded is different from the actual length of data";
    rf.ReadChildEnd();
  }
};

template<typename T>
class IOObject<vector<T> > {
public:
  static void WriteFileB(FILE *fp, const vector<T> &src) {
    zsize len = src.size();
    IOObj::WriteFileB(fp, len);
    if (len != 0) IOObject<T>::WriteFileB(fp, src.data(), len);
  }
  static void ReadFileB(FILE *fp, vector<T> &dst) {
    zsize len;
    IOObj::ReadFileB(fp, len);
    if (len != 0) {
      dst.resize(len);
      IOObj::ReadFileB(fp, dst.data(), len);
    } else {
      dst.clear();
    }
  }
  static void WriteFileR(RecordFile &rf, const zint32 label, const vector<T> &src) {
    zsize len = src.size();
    IOObj::WriteFileR(rf, label, len);
    if (len != 0) IOObject<T>::WriteFileR(rf, src.data(), src.size());
  }
  static void ReadFileR(RecordFile &rf, const zint32 label, vector<T> &dst) {
    if (!rf.LabelExist(label)) {
      dst.clear();
      return;
    }
    zsize len;
    IOObj::ReadFileR(rf, label, len);
    if (len != 0) {
      dst.resize(len);
      IOObj::ReadFileR(rf, dst.data(), dst.size());
    } else {
      dst.clear();
    }
  }
  /// When the object inside STLVector is not SIMPLE_IOOBJECT, it must access 1 by 1,
  /// instead of access as a raw array.
  static const int RF_SIZE = 1;
  static const int RF_DATA = 2;
  static void WriteFileR1By1(RecordFile &rf, const zint32 label, const vector<T> &src) {
    zuint64 len = src.size();
    rf.WriteChildBegin(label);
      IOObj::WriteFileR(rf, RF_SIZE, len);
      rf.WriteRepeatBegin(RF_DATA);
      for (zuint64 i = 0; i < len; ++i) {
        rf.WriteRepeatChild();
        IOObj::WriteFileR(rf, src[i]);
      }
      rf.WriteRepeatEnd();
    rf.WriteChildEnd();
  }
  static void ReadFileR1By1(RecordFile &rf, const zint32 label, vector<T> &dst) {
    dst.clear();
    if (!rf.LabelExist(label)) {
      return;
    }
    rf.ReadChildBegin(label);
      zuint64 len;
      IOObj::ReadFileR(rf, RF_SIZE, len);
      if (len != 0) {
        dst.reserve(len);
      }
      rf.ReadRepeatBegin(RF_DATA);
      while(rf.ReadRepeatChild()) {
        T v;
        IOObj::ReadFileR(rf, v);
        dst.push_back(v);
      }
      rf.ReadRepeatEnd();
      ZCHECK_EQ(dst.size(), len) << "The length recorded is different from the actual length of data";
    rf.ReadChildEnd();
  }
};

template<>
class IOObject<std::string> {
public:
  static void WriteFileB(FILE *fp, const std::string &src) {
    zsize len = src.size();
    IOObj::WriteFileB(fp, len);
    if (len != 0) IOObj::WriteFileB(fp, src.data(), len);
  }
  static void ReadFileB(FILE *fp, std::string &dst) {
    zsize len;
    IOObj::ReadFileB(fp, len);
    if (len != 0) {
      char *str = new char[len+1];
      IOObj::ReadFileB(fp, str, len);
      str[len]='\0';
      dst=str;
    } else {
      dst.clear();
    }
  }
  static void WriteFileR(RecordFile &rf, const zint32 label, const std::string &src) {
    zsize len = src.size();
    IOObj::WriteFileR(rf, label, len);
    if (len != 0) IOObj::WriteFileR(rf, src.data(), src.size());
  }
  static void ReadFileR(RecordFile &rf, const zint32 label, std::string &dst) {
    if (!rf.LabelExist(label)) {
      dst.clear();
      return;
    }
    zsize len;
    IOObj::ReadFileR(rf, label, len);
    if (len != 0) {
      char *str = new char[len+1];
      IOObj::ReadFileR(rf, str, len);
      str[len]='\0';
      dst=str;
    } else {
      dst.clear();
    }
  }
};

template<typename T1, typename T2>
class IOObject<map<T1, T2> > {
private:
  static const zint32 RF_SIZE = 1;
  static const zint32 RF_ITEM = 2;
  static const zint32 RF_ITEM_KEY = 1;
  static const zint32 RF_ITEM_VALUE = 2;
public:
  static void WriteFileR(RecordFile &rf, const zint32 label, const map<T1, T2> &src) {
    zsize len = src.size();
    rf.WriteChildBegin(label);
    IOObj::WriteFileR(rf, RF_SIZE, len);
    rf.WriteRepeatBegin(RF_ITEM);
    for (map<T1, T2>::const_iterator mi = src.begin(); mi != src.end(); ++mi) {
      rf.WriteRepeatChild();
      IOObj::WriteFileR(rf, RF_ITEM_KEY, mi->first);
      IOObj::WriteFileR(rf, RF_ITEM_VALUE, mi->second);
    }
    rf.WriteRepeatEnd();
    rf.WriteChildEnd();
  }
  static void ReadFileR(RecordFile &rf, const zint32 label, map<T1, T2> &dst) {
    dst.clear();
    zsize len = 0;
    if (!rf.LabelExist(label))
      return;
    rf.ReadChildBegin(label);
    IOObj::ReadFileR(rf, RF_SIZE, len);
    rf.ReadRepeatBegin(RF_ITEM);
    while (rf.ReadRepeatChild()) {
      T1 key;
      T2 value;
      IOObj::ReadFileR(rf, RF_ITEM_KEY, key);
      IOObj::ReadFileR(rf, RF_ITEM_VALUE, value);
      dst[key] = value;
    }
    rf.ReadRepeatEnd();
    rf.ReadChildEnd();
    ZCHECK_EQ(len, dst.size());
  }
};

}

